/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "cp_writer.h"

#include <string>
#include <sys/types.h>
#include <cinttypes>

#include "hmfs_utils.h"
#include "mkfs_format.h"
#include "securec.h"
#include "device_manager.h"
#include "hmfs_define.h"
#include "hmfs_io.h"
#include "hmfs_common.h"
#include "hmfs_quota.h"
#include "hmfs_zoned.h"


namespace OHOS {
namespace Hmfs {

CpWriter::CpWriter(HmfsMkfs *mkfs) : mkfs_(mkfs)
{

}

int32_t CpWriter::Prepare()
{
    if (PrepareCheckPointData()) {
        return -1;
    }

    if (PrepareActiveSegments()) {
        return -1;
    }

    if (PrepareNodeSummary()) {
        return -1;
    }

    if (PrepareNatBit()) {
        return -1;
    }

    if (PrepareEmptyBlock()) {
        return -1;
    }

    return 0;
}

int32_t CpWriter::PrepareCheckPointData()
{
    CmdConfig& cfgPara = mkfs_->cfgPara_;
    HmfsData& hmfsData = mkfs_->hmfsData_;

    cp_ = std::make_unique<CpOnDisk>();
    if (cp_ == nullptr) {
        return -1;
    }
    memset_s(cp_.get(), sizeof(CpOnDisk), 0, sizeof(CpOnDisk));
    struct CheckPointData* checkPoint = &cp_->cp;

    uint32_t segNo = 0;
    SetLeValue(checkPoint->curNodeSegNo[segNo], hmfsData.curSeg[CURSEG_NODE_HOT]);
    SetLeValue(checkPoint->curDataSegNo[segNo++], hmfsData.curSeg[CURSEG_DATA_HOT]);
    SetLeValue(checkPoint->curNodeSegNo[segNo], hmfsData.curSeg[CURSEG_NODE_WARM]);
    SetLeValue(checkPoint->curDataSegNo[segNo++], hmfsData.curSeg[CURSEG_DATA_WARM]);
    SetLeValue(checkPoint->curNodeSegNo[segNo], hmfsData.curSeg[CURSEG_NODE_COLD]);
    SetLeValue(checkPoint->curDataSegNo[segNo++], hmfsData.curSeg[CURSEG_DATA_COLD]);
    for (; segNo < HMFS_MAX_ACTIVE_NODE_LOGS; segNo++) {
        SetLeValue(checkPoint->curNodeSegNo[segNo], 0xffffffff);
        SetLeValue(checkPoint->curDataSegNo[segNo], 0xffffffff);
    }

    SetLeValue(checkPoint->curNodeBlkOffset[0], 1 + hmfsData.quotaInodeCount + hmfsData.lpfInum);
    SetLeValue(checkPoint->curDataBlkOffset[0], 1 + hmfsData.quotaDataBlks + hmfsData.lpfDnum);
    SetLeValue(checkPoint->validBlockCount, 2 + hmfsData.quotaInodeCount + hmfsData.quotaDataBlks + hmfsData.lpfInum + hmfsData.lpfDnum);
    SetLeValue(checkPoint->rsvdSegmentCount, hmfsData.reservedSegments);

    struct SuperBlockData* superBlock = mkfs_->GetSuperBlockData();
    ASSERT(superBlock != nullptr);
    uint32_t usableSegments = HmfsZoned::GetInstance().HmfsGetUsableSegments(superBlock);
    uint32_t overprovSegmentCount = (usableSegments - hmfsData.reservedSegments) *
        cfgPara.overProvision / 100 + hmfsData.reservedSegments;
    SetLeValue(checkPoint->overprovSegmentCount, overprovSegmentCount);

    if (usableSegments <= overprovSegmentCount) {
        HMFS_ERROR("Not enough segments to create HMFS Volume\n");
        return -1;
    }
    HMFS_INFO("Overprovision ratio = %.3lf%%\n", cfgPara.overProvision);
    HMFS_INFO("Overprovision segments = %u (GC reserved = %u)\n", overprovSegmentCount, hmfsData.reservedSegments);

    uint32_t freeSegmentCount = usableSegments - ((cfgPara.features & HMFS_FEATURE_RO) ? 2 : 6);
    SetLeValue(checkPoint->freeSegmentCount, freeSegmentCount);
    SetLeValue(checkPoint->userBlockCount, (usableSegments - overprovSegmentCount) * BLOCKS_PER_SEGMENT);

    /* cp page (2), data summaries (1), node summaries (3) */
    SetLeValue(checkPoint->cpPackBlockCount, 6 + hmfsData.cpPayload);

    natBitmapSize_ = hmfsData.segmentCountInNAT / 2 * 512 / 8;
    natBitsAreablocks_ = AlignUpCount(sizeof(uint64_t) + natBitmapSize_ + natBitmapSize_, HMFS_BLOCK_SIZE);

    uint32_t flags = CP_FLAG_UMOUNT | CP_FLAG_COMPACT_SUM;
    if (GetLeValue(checkPoint->cpPackBlockCount) <= BLOCKS_PER_SEGMENT - natBitsAreablocks_) {
        flags |= CP_FLAG_NAT_BITS;
    }

    if (cfgPara.trim) {
        flags |= CP_TRIMMED_FLAG;
    }

    if (cfgPara.largeNatBitmap) {
        flags |= CP_LARGE_NAT_BITMAP_FLAG;
    }

    SetLeValue(checkPoint->cpFlags, flags);
    SetLeValue(checkPoint->cpPackStartSum, 1 + hmfsData.cpPayload);
    SetLeValue(checkPoint->validNodeCount, 1 + hmfsData.quotaInodeCount + hmfsData.lpfInum);
    SetLeValue(checkPoint->validInodeCount, 1 + hmfsData.quotaInodeCount + hmfsData.lpfInum);
    SetLeValue(checkPoint->nextFreeNodeId, hmfsData.nextFreeInodeId);
    SetLeValue(checkPoint->sitVersionBitmapSize,
        (hmfsData.segmentCountInSIT / HMFS_SIT_COUNT) * BLOCKS_PER_SEGMENT / BITS_PER_BYTE);
    SetLeValue(checkPoint->natVersionBitmapSize,
        (hmfsData.segmentCountInNAT / HMFS_NAT_COUNT) * BLOCKS_PER_SEGMENT / BITS_PER_BYTE);

    srand(cfgPara.fakeSeed ? 0 : time(NULL));
    SetLeValue(checkPoint->cpVersion, rand() | 0x1);
    HMFS_DEBUG("checkPoint->cpVersion = %" PRIx64 "", checkPoint->cpVersion);

    return 0;
}

void CpWriter::MakeCheckPointCrc()
{
    CmdConfig& cfgPara = mkfs_->cfgPara_;
    struct CheckPointData* checkPoint = &cp_->cp;
    uint32_t checksumOffset = cfgPara.largeNatBitmap ? CP_MIN_CHKSUM_OFFSET : CP_CHECKSUM_OFFSET;
    SetLeValue(checkPoint->checksumOffset, checksumOffset);

    uint32_t crc = HmfsCommon::GetInstance().HmfsCheckpointChksum(checkPoint);
    HMFS_DEBUG("CP crc = 0x%x", crc);
    SetLeValue(*((uint32_t *)((uint8_t *)checkPoint + checksumOffset)), crc);
}

int32_t CpWriter::PrepareActiveSegments()
{
    HmfsData& hmfsData = mkfs_->hmfsData_;
    CmdConfig& cfgPara = mkfs_->cfgPara_;
    struct CheckPointData* checkPoint = &cp_->cp;

    activeSegments_ = std::make_unique<uint8_t[]>(HMFS_BLOCK_SIZE);
    if (activeSegments_ == nullptr) {
        return -1;
    }
    memset_s(activeSegments_.get(), HMFS_BLOCK_SIZE, 0, HMFS_BLOCK_SIZE);

    JournalEntry* natJournal = reinterpret_cast<JournalEntry*>(activeSegments_.get());
    uint32_t entryIndex = 0;
    SetLeValue(natJournal->nNats, 1 + hmfsData.quotaInodeCount + hmfsData.lpfInum);
    SetLeValue(natJournal->natJ.entries[entryIndex].nid, hmfsData.rootInode);
    natJournal->natJ.entries[entryIndex].ne.version = 0;
    natJournal->natJ.entries[entryIndex].ne.inodeNo = natJournal->natJ.entries[entryIndex].nid;
    SetLeValue(natJournal->natJ.entries[entryIndex].ne.blockId,
        hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_NODE_HOT] * BLOCKS_PER_SEGMENT);
    entryIndex++;

    struct SuperBlockData* superBlock = mkfs_->GetSuperBlockData();
    if (superBlock == nullptr) {
        return -1;
    }

    for (int32_t qtype = 0; qtype < MAXQUOTAS; qtype++) {
        if (!((1 << qtype) & cfgPara.quotaBits)) {
            continue;
        }

        natJournal->natJ.entries[entryIndex].nid = superBlock->qfInodeId[qtype];
        natJournal->natJ.entries[entryIndex].ne.version = 0;
        natJournal->natJ.entries[entryIndex].ne.inodeNo = superBlock->qfInodeId[qtype];
        SetLeValue(natJournal->natJ.entries[entryIndex].ne.blockId,
            hmfsData.mainStartBlkId + checkPoint->curNodeSegNo[0] * BLOCKS_PER_SEGMENT + entryIndex);
        entryIndex++;
    }

    if (hmfsData.lpfInum) {
        natJournal->natJ.entries[entryIndex].nid = NATIVE_TO_LE32(hmfsData.lpfIno);
        natJournal->natJ.entries[entryIndex].ne.version = 0;
        natJournal->natJ.entries[entryIndex].ne.inodeNo = NATIVE_TO_LE32(hmfsData.lpfIno);
        SetLeValue(natJournal->natJ.entries[entryIndex].ne.blockId,
            hmfsData.mainStartBlkId + checkPoint->curNodeSegNo[0] * BLOCKS_PER_SEGMENT + entryIndex);
    }

    JournalEntry* sitJournal = natJournal + 1;
    SetLeValue(sitJournal->nSits, (cfgPara.features & HMFS_FEATURE_RO) ? 2 : 6);
    sitJournal->sitJ.entries[0].segno = checkPoint->curNodeSegNo[0];
    SetLeValue(sitJournal->sitJ.entries[0].se.usedBlockCount,
        (CURSEG_NODE_HOT << 10) + 1 + hmfsData.quotaInodeCount + hmfsData.lpfInum);

    uint32_t bitId = 0;
    HmfsCommon::GetInstance().HmfsSetBit(bitId++, (char *)sitJournal->sitJ.entries[0].se.blockBitmap);
    for (uint32_t i = 0; i < hmfsData.quotaInodeCount; i++) {
        HmfsCommon::GetInstance().HmfsSetBit(bitId++, (char *)sitJournal->sitJ.entries[0].se.blockBitmap);
    }
    if (hmfsData.lpfInum) {
        HmfsCommon::GetInstance().HmfsSetBit(bitId, (char *)sitJournal->sitJ.entries[0].se.blockBitmap);
    }

    if (cfgPara.features & HMFS_FEATURE_RO) {
        /* data sit for root */
        sitJournal->sitJ.entries[1].segno = checkPoint->curDataSegNo[0];
        SetLeValue(sitJournal->sitJ.entries[1].se.usedBlockCount,
            (CURSEG_DATA_HOT << 10) | (1 + hmfsData.quotaDataBlks + hmfsData.lpfDnum));
        bitId = 0;
        HmfsCommon::GetInstance().HmfsSetBit(bitId++, (char *)sitJournal->sitJ.entries[1].se.blockBitmap);
        for (uint32_t i = 0; i < hmfsData.quotaDataBlks; i++) {
            HmfsCommon::GetInstance().HmfsSetBit(bitId++, (char *)sitJournal->sitJ.entries[1].se.blockBitmap);
        }
        if (hmfsData.lpfDnum) {
            HmfsCommon::GetInstance().HmfsSetBit(bitId, (char *)sitJournal->sitJ.entries[1].se.blockBitmap);
        }
    } else {
        sitJournal->sitJ.entries[1].segno = checkPoint->curNodeSegNo[1];
        SetLeValue(sitJournal->sitJ.entries[1].se.usedBlockCount, CURSEG_NODE_WARM << 10);
        sitJournal->sitJ.entries[2].segno = checkPoint->curNodeSegNo[2];
        SetLeValue(sitJournal->sitJ.entries[2].se.usedBlockCount, CURSEG_NODE_COLD << 10);

        /* data sit for root */
        sitJournal->sitJ.entries[3].segno = checkPoint->curDataSegNo[0];
        SetLeValue(sitJournal->sitJ.entries[3].se.usedBlockCount,
            (CURSEG_DATA_HOT << 10) | (1 + hmfsData.quotaDataBlks + hmfsData.lpfDnum));

        bitId = 0;
        HmfsCommon::GetInstance().HmfsSetBit(bitId++, (char *)sitJournal->sitJ.entries[3].se.blockBitmap);
        for (uint32_t i = 0; i < hmfsData.quotaDataBlks; i++) {
            HmfsCommon::GetInstance().HmfsSetBit(bitId++, (char *)sitJournal->sitJ.entries[3].se.blockBitmap);
        }
        if (hmfsData.lpfDnum) {
            HmfsCommon::GetInstance().HmfsSetBit(bitId, (char *)sitJournal->sitJ.entries[3].se.blockBitmap);
        }

        sitJournal->sitJ.entries[4].segno = checkPoint->curDataSegNo[1];
        sitJournal->sitJ.entries[4].se.usedBlockCount = NATIVE_TO_LE32((CURSEG_DATA_WARM << 10));
        sitJournal->sitJ.entries[5].segno = checkPoint->curDataSegNo[2];
        sitJournal->sitJ.entries[5].se.usedBlockCount = NATIVE_TO_LE16((CURSEG_DATA_COLD << 10));
    }

    SummaryEntry* sum_entry = reinterpret_cast<SummaryEntry*>(sitJournal + 1);
    SetLeValue(sum_entry->nid, hmfsData.rootInode);
    sum_entry->ofsInNode = 0;

    uint32_t offset = 1;
    for (int32_t qtype = 0; qtype < MAXQUOTAS; qtype++) {
        if (!((1 << qtype) & cfgPara.quotaBits)) {
            continue;
        }

        for (int32_t i = 0; i < QUOTA_DATA_BLOCK_COUNT; i++) {
            (sum_entry + offset + i)->nid = superBlock->qfInodeId[qtype];;
            (sum_entry + offset + i)->ofsInNode = NATIVE_TO_LE16(i);
        }
        offset += QUOTA_DATA_BLOCK_COUNT;
    }

    if (hmfsData.lpfDnum) {
        (sum_entry + offset)->nid = NATIVE_TO_LE32(hmfsData.lpfIno);
        (sum_entry + offset)->ofsInNode = 0;
    }

    return 0;
}


int32_t CpWriter::PrepareNodeSummary()
{
    CmdConfig& cfgPara = mkfs_->cfgPara_;
    HmfsData& hmfsData = mkfs_->hmfsData_;
    struct SuperBlockData* superBlock = mkfs_->GetSuperBlockData();
    if (superBlock == nullptr) {
        return -1;
    }

    hotNodeSumary_ = std::make_unique<SummaryBlockData>();
    if (hotNodeSumary_ == nullptr) {
        return -1;
    }
    memset_s(hotNodeSumary_.get(), sizeof(SummaryBlockData), 0, sizeof(SummaryBlockData));

    hotNodeSumary_->footer.entryType = SUMMARY_TYPE_NODE;

    uint32_t id = 0;
    SetLeValue(hotNodeSumary_->entries[id].nid, hmfsData.rootInode);
    hotNodeSumary_->entries[id++].ofsInNode = 0;
    for (int32_t qtype = 0; qtype < MAXQUOTAS; qtype++) {
        if (!((1 << qtype) & cfgPara.quotaBits)) {
            continue;
        }

        hotNodeSumary_->entries[id].nid = superBlock->qfInodeId[qtype];
        hotNodeSumary_->entries[id++].ofsInNode = 0;
    }
    if (hmfsData.lpfInum) {
        hotNodeSumary_->entries[id].nid = NATIVE_TO_LE32(hmfsData.lpfIno);
        hotNodeSumary_->entries[id].ofsInNode = 0;
    }

    warmColdNodeSumary_ = std::make_unique<SummaryBlockData>();
    if (warmColdNodeSumary_ == nullptr) {
        return -1;
    }
    memset_s(warmColdNodeSumary_.get(), sizeof(SummaryBlockData), 0, sizeof(SummaryBlockData));

    warmColdNodeSumary_->footer.entryType = SUMMARY_TYPE_NODE;

    return 0;
}

int32_t CpWriter::PrepareNatBit()
{
    struct CheckPointData* checkPoint = &cp_->cp;

    if (!(GetLeValue(checkPoint->cpFlags) & CP_FLAG_NAT_BITS)) {
        return 0;
    }

    /* +---------+------------------+-------------------+
     * | version | full nat bit map | empty nat bit map |
     * +---------+------------------+-------------------+
     *  uint64_t    natBitmapSize_     natBitmapSize_
     *                 all zero
     */
    uint32_t natBitAreaSize = HMFS_BLOCK_SIZE * natBitsAreablocks_;
    natBits_ = std::make_unique<uint8_t[]>(natBitAreaSize);
    if (natBits_ == nullptr) {
        return -1;
    }
    memset_s(natBits_.get(), natBitAreaSize, 0, natBitAreaSize);

    uint64_t* version = reinterpret_cast<uint64_t*>(natBits_.get());
    *version = HmfsCommon::GetInstance().GetCpCrc(checkPoint);

    uint8_t* emptyNatBitmap = natBits_.get() + sizeof(uint64_t) + natBitmapSize_;
    memset_s(emptyNatBitmap, natBitmapSize_, 0xff, natBitmapSize_);
    HmfsCommon::GetInstance().TestAndClearBitLe(0, emptyNatBitmap);

    return 0;
}

int32_t CpWriter::PrepareEmptyBlock()
{
    emptyBlock_ = std::make_unique<uint8_t[]>(HMFS_BLOCK_SIZE);
    if (emptyBlock_ == nullptr) {
        return -1;
    }
    memset_s(emptyBlock_.get(), HMFS_BLOCK_SIZE, 0, HMFS_BLOCK_SIZE);
    return 0;
}


int32_t CpWriter::Write()
{
    HmfsData& hmfsData = mkfs_->hmfsData_;

    writeBlockId_ = hmfsData.segment0BlkId;

    MakeCheckPointCrc();
    if (WriteCpStruct()) {
        return -1;
    }

    if (WriteCpPayload()) {
        return -1;
    }

    if (WriteActiveSegments()) {
        return -1;
    }

    if (WriteCpStruct()) {
        return -1;
    }

    if (WriteNatBit()) {
        return -1;
    }

    // second segment
    struct CheckPointData* checkPoint = &cp_->cp;
    checkPoint->cpVersion = 0;
    MakeCheckPointCrc();
    writeBlockId_ = hmfsData.segment0BlkId + BLOCKS_PER_SEGMENT;
    if (WriteCpStruct()) {
        return -1;
    }

    if (WriteCpPayload()) {
        return -1;
    }

    writeBlockId_ += GetLeValue(checkPoint->cpPackBlockCount) - hmfsData.cpPayload - 2;
    if (WriteCpStruct()) {
        return -1;
    }

    return 0;
}

int32_t CpWriter::WriteCpStruct()
{
    if (HmfsIo::GetInstance().DevWriteBlock(cp_.get(), writeBlockId_)) {
        HMFS_ERROR("failed to write the cp to disk");
        return -1;
    }
    writeBlockId_++;

    return 0;
}

int32_t CpWriter::WriteCpPayload()
{
    HmfsData& hmfsData = mkfs_->hmfsData_;

    for (uint32_t i = 0; i < hmfsData.cpPayload; i++) {
        if (HmfsIo::GetInstance().DevFillBlock(emptyBlock_.get(), writeBlockId_)) {
            HMFS_ERROR("failed to write the sit bitmap area to disk");
            return -1;
        }
        writeBlockId_++;
    }
    
    return 0;
}

int32_t CpWriter::WriteActiveSegments()
{
    
    if (HmfsIo::GetInstance().DevWriteBlock(activeSegments_.get(), writeBlockId_)) {
        HMFS_ERROR("failed to write the activeSegments to disk, blockid = %" PRIu64 "", writeBlockId_);
        return -1;
    }
    writeBlockId_++;

    // hot node summary
    if (HmfsIo::GetInstance().DevWriteBlock(hotNodeSumary_.get(), writeBlockId_)) {
        HMFS_ERROR("failed to write the hot Node Sumary to disk, blockid = %" PRIu64 "", writeBlockId_);
        return -1;
    }
    writeBlockId_++;

    // warm node summary
    if (HmfsIo::GetInstance().DevWriteBlock(warmColdNodeSumary_.get(), writeBlockId_)) {
        HMFS_ERROR("failed to write the warm node summary to disk, blockid = %" PRIu64 "", writeBlockId_);
        return -1;
    }
    writeBlockId_++;

    // cold node summary
    if (HmfsIo::GetInstance().DevWriteBlock(warmColdNodeSumary_.get(), writeBlockId_)) {
        HMFS_ERROR("failed to write the cold node summary to disk, blockid = %" PRIu64 "", writeBlockId_);
        return -1;
    }
    writeBlockId_++;

    return 0;
}

int32_t CpWriter::WriteNatBit()
{
    HmfsData& hmfsData = mkfs_->hmfsData_;

    writeBlockId_ = hmfsData.segment0BlkId + BLOCKS_PER_SEGMENT - natBitsAreablocks_;

    for (uint32_t i = 0; i < natBitsAreablocks_; i++) {
        if (HmfsIo::GetInstance().DevWriteBlock(natBits_.get() + i * HMFS_BLOCK_SIZE, writeBlockId_)) {
            HMFS_ERROR("failed to write NAT bits!\n");
            return -1;
        }
        writeBlockId_++;
    }
    return 0;
}




} // namespace Hmfs
} // namespace OHOS
